package com.googlecode.alfrescocachingserver.extractor;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.springframework.extensions.webscripts.DeclarativeRegistry;
import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.Match;

import com.googlecode.alfrescocachingserver.exceptions.ServiceException;
import com.googlecode.alfrescocachingserver.model.WebScriptCacheKey;
import com.googlecode.alfrescocachingserver.model.WebScriptResponse;
import com.googlecode.alfrescocachingserver.model.WebScriptUri;

public class UriTemplateIdExtractor implements CacheIdExtractor
{
    protected DeclarativeRegistry uriRegistry;

    protected boolean initialized = false;
    protected Set<String> queryParameters;
    protected Set<String> templateVariables;

    public UriTemplateIdExtractor()
    {
    }

    private UriTemplateIdExtractor(String uriTemplateStr, Description description, String[] parameters)
    {
        if (parameters == null || parameters.length == 0)
        {
            throw new ServiceException("UriTemplateIdExtractor requires at least one token name passed in as a parameter");
        }
        
        setUriTemplate(uriTemplateStr, description, parameters);
        
        this.initialized = true;
    }
    
    public void setUriRegistry(DeclarativeRegistry uriRegistry)
    {
        this.uriRegistry = uriRegistry;
    }

    @Override
    public CacheIdExtractor createInstance(String uriTemplateStr, Description description, String[] parameters)
    {
        UriTemplateIdExtractor uriTemplateIdExtractor = new UriTemplateIdExtractor(uriTemplateStr, description, parameters);
        uriTemplateIdExtractor.setUriRegistry(uriRegistry);
        
        return uriTemplateIdExtractor;
    }

    @Override
    public Set<String> retrieveIds(WebScriptCacheKey webScriptCacheKey, WebScriptResponse webScriptResponse)
    {
        Set<String> ids = new HashSet<String>();
        
        if (templateVariables != null)
        {
            WebScriptUri webScriptUri = webScriptCacheKey.getWebScriptUri();
            
            Match match = uriRegistry.findWebScript(webScriptUri.getHttpMethod().name(), webScriptUri.getUri());
            Map<String, String> templateValues = match.getTemplateVars();  
                    // uriTemplate.match(webScriptCacheKey.getWebScriptUri().getUri());
            
            for (String templateVariable : templateVariables)
            {
                String id = templateValues.get(templateVariable);

                if (StringUtils.isNotBlank(id))
                {
                    ids.add(id);
                }
            }
        }
        
        String queryString = webScriptCacheKey.getWebScriptUri().getQueryString();
        
        if (queryParameters != null && StringUtils.isNotBlank(queryString))
        {
            for (String queryParamNvp : queryString.split("&"))
            {
                String[] nameValue = queryParamNvp.split("=");
                
                if (nameValue.length == 2)
                {
                    if (queryParameters.contains(nameValue[0]) && StringUtils.isNotBlank(nameValue[1]))
                    {
                        ids.add(nameValue[1]);
                    }
                }
            }
        }
        
        return ids;
    }
    
    private void setUriTemplate(String uriTemplateStr, Description description, String[] tokens)
    {
        String uri = null;
        String queryString = null;

        // trim uri parameters
        int queryArgIdx = uriTemplateStr.indexOf('?');
        if (queryArgIdx != -1)
        {
            uri = uriTemplateStr.substring(0, queryArgIdx);
            queryString = uriTemplateStr.substring(queryArgIdx + 1);
        }
        else
        {
            uri = uriTemplateStr;
        }

        setTemplate(uri, tokens);
        setQueryParameters(queryString, tokens);
        
//        // trim extension, only if script distinguishes response format via the extension
//        if (description.getFormatStyle() != Description.FormatStyle.argument)
//        {
//            int extIdx = uri.lastIndexOf(".");
//            if (extIdx != -1)
//            {
//                uri = uri.substring(0, extIdx);
//            }
//        }
//        
//        this.uriTemplate = new UriTemplate(uri);
    }
    
    private void setTemplate(String uri, String[] tokens)
    {
        StringBuilder buf = new StringBuilder("(");
        
        for (String token : tokens)
        {
            buf.append("\\{")
               .append(token)
               .append("\\}|");
        }
        
        buf.deleteCharAt(buf.length() - 1);
        buf.append(")");
        
        Pattern pattern = Pattern.compile(buf.toString());
        
        Matcher matcher = pattern.matcher(uri);
        
        Set<String> templateVariables = new HashSet<String>();
        
        while (matcher.find())
        {
            templateVariables.add(matcher.group(1));
        }
        
        if (!templateVariables.isEmpty())
        {
            this.templateVariables = templateVariables;
        }
    }

    private void setQueryParameters(String queryString, String[] tokens)
    {
        if (StringUtils.isBlank(queryString))
        {
            queryParameters = null;
            return;
        }
        
        StringBuilder buf = new StringBuilder("(?:^|&)([^=]+)=(");
        
        for (String token : tokens)
        {
            buf.append("\\{")
               .append(token)
               .append("\\??\\}|");
        }
        
        buf.deleteCharAt(buf.length() - 1);
        buf.append(")");
        
        Pattern pattern = Pattern.compile(buf.toString());
        Matcher matcher = pattern.matcher(queryString);
        
        Set<String> queryParameters = new HashSet<String>();
        
        while (matcher.find())
        {
            queryParameters.add(matcher.group(1));
        }
        
        if (!queryParameters.isEmpty())
        {
            this.queryParameters = queryParameters;
        }
        
    }
}
